﻿using System;
using System.Collections.Generic;
using System.IO;

using NAnt.Core;
using NAnt.Core.Attributes;

namespace ReferenceSwap.Nant
{
    [TaskName("ref_swap")]
    public class ReferenceSwapTask : Task
    {
        private ReferenceCollection references = new ReferenceCollection();

        [BuildElementCollection("refs", "ref", Required = false)]
        public ReferenceCollection References
        {
            get { return references; }
            set { references = value; }
        }
        // todo: remove auto-properties so this works in older visual studio
        [TaskAttribute("all", Required = false)]
        public bool SwapAll { get; set; }

        [TaskAttribute("project", Required = true)]
        public FileInfo TargetProject { get; set; }

        [TaskAttribute("solution", Required = false)]
        public FileInfo TargetSolution { get; set; }

        [TaskAttribute("cleanSolution", Required = false)]
        public bool CleanSolution { get; set; }

        [TaskAttribute("build", Required = false)]
        public string Build { get; set; }

        [TaskAttribute("libDir", Required = false)]
        public string LibDirectory { get; set; }

        //[TaskAttribute("releaseDir", Required = false)]
        //public DirectoryInfo ReleaseDirectory { get; set; }

        protected override void ExecuteTask()
        {
            if (references.Count == 0 && !SwapAll)
            {
                Log(Level.Warning, "No references specified. Nothing to do.");
                return;
            }

            if (!File.Exists(TargetProject.FullName))
                throw new FileNotFoundException("Cannot find file.", TargetProject.FullName);

            if (string.IsNullOrEmpty(LibDirectory))
                LibDirectory = "libs";

            ProjectData project = new ProjectData(TargetProject.FullName);

            Log(Level.Verbose, "Found project {0}.", project.ProjectFile.Name);

            SolutionData solution = null;
            if (TargetSolution != null)
            {
                // assuming that you want to clean the solution if there is a solution file named
                solution = new SolutionData(TargetSolution.FullName);
                CleanSolution = true;
            }
            else if (CleanSolution)
            {
                solution = project.FindSolution();
            }

            ReferenceManager mgr = new ReferenceManager();
            // find the project references we want to swap out
            List<AssemblyReference> assmToSwap = new List<AssemblyReference>();
            List<ProjectReference> projToSwap = new List<ProjectReference>();
            foreach (Reference reference in project.References)
            {
                if (ShouldSwapReference(reference))
                {
                    Log(Level.Verbose, string.Format("Swapping reference: {0}", reference));
                    if (reference is ProjectReference)
                        projToSwap.Add((ProjectReference)reference);
                    else
                        assmToSwap.Add((AssemblyReference)reference);
                }
            }            
            
            mgr.SwapToAssemblyReference(project, projToSwap, LibDirectory, Build);
            mgr.MakeAssemblyReferenceLocal(project, assmToSwap, LibDirectory);

            if (CleanSolution)
                mgr.RemoveReferencesFromSolution(solution, projToSwap);
        }

        private bool ShouldSwapReference(Reference reference)
        {
            if (SwapAll)
                return true;

            foreach (ReferenceElement r in references)
            {
                switch (r.Type)
                {
                    case ReferenceType.Assembly:
                    case ReferenceType.Project:
                        return r.Value == reference.Name;
                    case ReferenceType.Match: // should probably do more?
                        return reference.Path.Contains(r.Value);
                }
            }

            return false;
        }
    }
}
